// 使用 buffered channel 实现对象池
package main

import (
	"errors"
	"fmt"
	"time"
)

// 可重复使用的对象结构体
type userStruct struct {
	name string
	age  int
}

type ObjPool struct {
	bufChan chan *userStruct //用于缓冲可重用对象
}

// 创建并初始化对象池
func NewObjPool(numOfObj int) *ObjPool {
	objPool := ObjPool{}
	objPool.bufChan = make(chan *userStruct, numOfObj)
	for i := 0; i < numOfObj; i++ {
		objPool.bufChan <- &userStruct{
			//假设提前预置好指定数量的连接信息
			//也可以在这里不预置,然后在 PutObj() 操作中再设置连接信息
			name: fmt.Sprintf("%v_%d", "user", i),
			age:  18,
		}
	}
	return &objPool
}

// 获取对象池中的数据
func (p *ObjPool) GetObj(timeout time.Duration) (*userStruct, error) {
	select {
	case ret := <-p.bufChan:
		return ret, nil

	//超时控制,不会让获取对象的地方一直阻塞,达到设定的超时时间后会返回一个错误信息
	case <-time.After(timeout):
		return nil, errors.New("获取数据超时")
	}

}

// 将数据放入到对象池中
func (p *ObjPool) PutObj(obj *userStruct) error {
	select {
	case p.bufChan <- obj:
		return nil

	//在某些情况下(比如size已经满了)会出现放不进去,为了防止阻塞,在default分支中抛出一个异常
	default:
		return errors.New("对象池容量已满")
	}
}

func main() {
	pool := NewObjPool(5)

	//尝试给已满的对象池中继续放入对象
	//if err := pool.PutObj(&userStruct{}); err != nil {
	//	fmt.Println("错误: ", err)
	//}
	for i := 0; i < 10; i++ {
		//设置1秒超时
		if v, err := pool.GetObj(time.Second * 1); err != nil {
			fmt.Println("获取对象出错: ", err)
		} else {
			fmt.Printf("%T, %v,%v \n", v, v, pool.bufChan)
			if err := pool.PutObj(v); err != nil {
				fmt.Println("放入对象出错: ", err)
			}
		}
	}
	fmt.Println("完成")
}
